// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: wsfuyibing <682805@qq.com>
// Date: 2024-07-25

// Package gen_annotation
// parse annotation from source code file and recognize follows.
//
// Comment @Handler("name", "pkg.ExampleHandler") to define handler and
// use @Middleware("name") to use it around Controller handler or route
// handler.
package gen_annotation

import (
	"context"
	"fmt"
	"gitee.com/go-libs/console"
	"gitee.com/go-wares/framework-iris/framework/src/commands"
	"path/filepath"
	"regexp"
)

// Provider
// is a component used to parse source to and save to annotation file.
type Provider struct {
	commands.Common
	Module, ModuleFolder                                                     string
	AnnotationPath, ControllerPath, CrontabPath, HandlerPath, MiddlewarePath string
	AnnotationPkg                                                            string
	WorkingDir                                                               string
	Override                                                                 bool
}

// Before
// validate command options and accesses.
func (o *Provider) Before(_ context.Context, container *console.Container, command *console.Command) (err error) {
	// Seek
	// go.mod file from working directory.
	if o.Module, o.ModuleFolder = command.GenerateModule(o.WorkingDir); o.Module == "" {
		err = fmt.Errorf(`can not find go.mod`)
		return
	}
	// Working directory.
	if opt, has := command.GetOption(OptWorkingDir); has {
		if str := opt.ToString(); str != "" {
			o.WorkingDir = str
		}
	}
	if o.WorkingDir != "" {
		if o.WorkingDir, err = filepath.Abs(o.WorkingDir); err != nil {
			return
		}
	}
	// Annotation path replace.
	if opt, has := command.GetOption(OptAnnotationPath); has {
		if str := opt.ToString(); str != "" {
			o.AnnotationPath = str
		}
	}

	// Package
	// for annotation files.
	if m := regexp.MustCompile(`([_a-zA-Z0-9]+)$`).FindStringSubmatch(o.AnnotationPath); len(m) > 0 {
		o.AnnotationPkg = m[1]
	}

	// Controller path replace.
	if opt, has := command.GetOption(OptControllerPath); has {
		if str := opt.ToString(); str != "" {
			o.ControllerPath = str
		}
	}
	// Crontab path replace.
	if opt, has := command.GetOption(OptCrontabPath); has {
		if str := opt.ToString(); str != "" {
			o.CrontabPath = str
		}
	}
	// Handler path replace.
	if opt, has := command.GetOption(OptHandlerPath); has {
		if str := opt.ToString(); str != "" {
			o.HandlerPath = str
		}
	}
	// Middleware path replace.
	if opt, has := command.GetOption(OptMiddlewarePath); has {
		if str := opt.ToString(); str != "" {
			o.MiddlewarePath = str
		}
	}
	// Override or not.
	if opt, has := command.GetOption(OptOverride); has {
		o.Override = opt.GetSpecified()
	}

	// Application info.
	container.GetOutput().Info("+ [application]")
	container.GetOutput().Info("  - go module : %s", o.Module)
	container.GetOutput().Info("  - go working: %s", o.WorkingDir)
	container.GetOutput().Info("    - annotation: %s", o.AnnotationPath)
	container.GetOutput().Info("    - controller: %s", o.ControllerPath)
	container.GetOutput().Info("    - crontab: %s", o.CrontabPath)
	container.GetOutput().Info("    - handler: %s", o.HandlerPath)
	container.GetOutput().Info("    - middleware: %s", o.MiddlewarePath)
	container.GetOutput().Info("  - override  : %v", o.Override)
	return
}

// Run
// provider process to seek files and generate.
func (o *Provider) Run(_ context.Context, container *console.Container, command *console.Command) (err error) {
	seeker := &Seeker{Provider: o, Container: container, Command: command}

	// Iterate
	// parsers for each path.
	for _, m := range []map[string]SeekerDo{
		{o.ControllerPath: seeker.DoController},
		{o.CrontabPath: seeker.DoCrontab},
		{o.HandlerPath: seeker.DoHandler},
		{o.MiddlewarePath: seeker.DoMiddleware},
	} {
		for path, do := range m {
			if err = do(path); err != nil {
				return
			}
		}
	}

	// Do seeker result.
	err = seeker.Do()
	return
}

// +---------------------------------------------------------------------------+
// | Access methods                                                            |
// +---------------------------------------------------------------------------+

// Init
// with default values.
func (o *Provider) init() *Provider {
	o.AnnotationPath = OptAnnotationPathDefaultValue
	o.ControllerPath = OptControllerPathDefaultValue
	o.CrontabPath = OptCrontabPathDefaultValue
	o.HandlerPath = OptHandlerPathDefaultValue
	o.MiddlewarePath = OptMiddlewarePathDefaultValue
	o.Override = false
	o.WorkingDir = OptWorkingDirDefaultValue
	return o
}
